﻿using System;
//using System.Collections.Generic;
//using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;

namespace ServerASISTSYS.Framework
{
    public class ASISTSYS_Socket
    {
        public static SocksProxy SocksProxy;
        public static bool UseSocks = false;

        public static Socket CreateTCPSocket(String address, int port)
        {
            return CreateTCPSocket(address, port, UseSocks, SocksProxy);
        }

        public static Socket CreateTCPSocket(String address, int port, bool useSocks, SocksProxy proxy)
        {
            Socket sock = null;

            if (useSocks)
                sock = ConnectToSocksProxy(proxy.host, proxy.port, address, port, proxy.username, proxy.password);
            else
            {
                //IPAddress host = Dns.GetHostByName(address).AddressList[0];
                IPAddress host = Dns.GetHostEntry(address).AddressList[0];
                sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                sock.Connect(new IPEndPoint(host, port));
            }

            return sock;
        }

        private static string[] errorMsgs =	{
			"Operation completed successfully.",
			"General SOCKS server failure.",
			"Connection not allowed by ruleset.",
			"Network unreachable.",
			"Host unreachable.",
			"Connection refused.",
			"TTL expired.",
			"Command not supported.",
			"Address type not supported.",
			"Unknown error."
		};

        public static Socket ConnectToSocksProxy(IPAddress proxyIP, int proxyPort, String destAddress, int destPort, string userName, string password)
        {
            byte[] request = new byte[257];
            byte[] response = new byte[257];
            ushort nIndex;

            IPAddress destIP = null;

            try { destIP = IPAddress.Parse(destAddress); }
            catch { }

            IPEndPoint proxyEndPoint = new IPEndPoint(proxyIP, proxyPort);

            // open a TCP connection to SOCKS server...
            Socket s;
            s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            s.Connect(proxyEndPoint);
            //} catch(SocketException){
            //    throw new SocketException(0, "Could not connect to proxy server.");
            //}

            nIndex = 0;
            request[nIndex++] = 0x05; // Version 5.
            request[nIndex++] = 0x02; // 2 Authentication methods are in packet...
            request[nIndex++] = 0x00; // NO AUTHENTICATION REQUIRED
            request[nIndex++] = 0x02; // USERNAME/PASSWORD
            // Send the authentication negotiation request...
            s.Send(request, nIndex, SocketFlags.None);

            // Receive 2 byte response...
            int nGot = s.Receive(response, 2, SocketFlags.None);
            if (nGot != 2)
                throw new ConnectionException("Bad response received from proxy server.");

            if (response[1] == 0xFF)
            {	// No authentication method was accepted close the socket.
                s.Close();
                throw new ConnectionException("None of the authentication method was accepted by proxy server.");
            }

            byte[] rawBytes;

            if (/*response[1]==0x02*/true)
            {//Username/Password Authentication protocol
                nIndex = 0;
                request[nIndex++] = 0x05; // Version 5.

                // add user name
                request[nIndex++] = (byte)userName.Length;
                rawBytes = Encoding.Default.GetBytes(userName);
                rawBytes.CopyTo(request, nIndex);
                nIndex += (ushort)rawBytes.Length;

                // add password
                request[nIndex++] = (byte)password.Length;
                rawBytes = Encoding.Default.GetBytes(password);
                rawBytes.CopyTo(request, nIndex);
                nIndex += (ushort)rawBytes.Length;

                // Send the Username/Password request
                s.Send(request, nIndex, SocketFlags.None);
                // Receive 2 byte response...
                nGot = s.Receive(response, 2, SocketFlags.None);
                if (nGot != 2)
                    throw new ConnectionException("Bad response received from proxy server.");
                if (response[1] != 0x00)
                    throw new ConnectionException("Bad Usernaem/Password.");
            }
            // This version only supports connect command. 
            // UDP and Bind are not supported.

            // Send connect request now...
            nIndex = 0;
            request[nIndex++] = 0x05;	// version 5.
            request[nIndex++] = 0x01;	// command = connect.
            request[nIndex++] = 0x00;	// Reserve = must be 0x00

            if (destIP != null)
            {// Destination adress in an IP.
                switch (destIP.AddressFamily)
                {
                    case AddressFamily.InterNetwork:
                        // Address is IPV4 format
                        request[nIndex++] = 0x01;
                        rawBytes = destIP.GetAddressBytes();
                        rawBytes.CopyTo(request, nIndex);
                        nIndex += (ushort)rawBytes.Length;
                        break;
                    case AddressFamily.InterNetworkV6:
                        // Address is IPV6 format
                        request[nIndex++] = 0x04;
                        rawBytes = destIP.GetAddressBytes();
                        rawBytes.CopyTo(request, nIndex);
                        nIndex += (ushort)rawBytes.Length;
                        break;
                }
            }
            else
            {// Dest. address is domain name.
                request[nIndex++] = 0x03;	// Address is full-qualified domain name.
                request[nIndex++] = Convert.ToByte(destAddress.Length); // length of address.
                rawBytes = Encoding.Default.GetBytes(destAddress);
                rawBytes.CopyTo(request, nIndex);
                nIndex += (ushort)rawBytes.Length;
            }

            // using big-edian byte order
            byte[] portBytes = BitConverter.GetBytes((ushort)destPort);
            for (int i = portBytes.Length - 1; i >= 0; i--)
                request[nIndex++] = portBytes[i];

            // send connect request.
            s.Send(request, nIndex, SocketFlags.None);
            s.Receive(response);	// Get variable length response...
            if (response[1] != 0x00)
                throw new ConnectionException(errorMsgs[response[1]]);
            // Success Connected...
            return s;
        }
    }
}
